package nullInvader.Engine;

import java.awt.Image;
import java.awt.Point;
import java.util.EmptyStackException;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

/**
 * Applikationsutveckling i Java, 5DV135 HT12 Project
 * 
 * File: Unit.java Description: An abstract class that any unit have to
 * implement holds methods to ensure that implementing a new unit goes smoothly
 * since the basic methods is already
 * 
 * Date: 17 december 2012
 * 
 * cs-user: dv11flh
 * 
 * @author Fredrik Loch
 */
public abstract class Unit {

    //
    // Fields
    //

    protected Point position;
    protected Point nextPosition;
    protected int health;
    protected int speed;
    protected int currentSpeed;
    protected int price;
    protected boolean dead;
    protected boolean clickable;
    protected Image icon;
    protected Player p;
    protected Stack<Point> path;
    protected List<Integer> visited;

    //
    // Constructors
    //

    public Unit(Point startPos, Player p, int price) {
	speed = 1;
	currentSpeed = speed;
	position = (Point) startPos.clone();
	dead = false;
	clickable = false;
	this.price = price;
	this.p = p;
	nextPosition = null;
	path = new Stack<Point>();
	path.push(position);
	visited = new LinkedList<Integer>();
	moveUnitTo();
    };

    //
    // Accessor methods
    //

    /**
     * Method to get the price of the specific unit
     * 
     * @return int
     */
    public int getPrice() {
	return price;
    }

    /**
     * Method to get the image that represents the units
     * 
     * @return Image
     */
    public Image getIcon() {
	return icon;
    }

    /**
     * Method to set new icon for the unit
     */
    protected abstract void setIcon();

    /**
     * Specifies the next step for unit used to handle junctions
     * 
     * @param p
     */
    public void setPosition(Point p) {
	position = p;
	path.push((Point) position.clone());
    }

    /**
     * Sets the units position used for teleporting units
     * 
     * @param p
     */
    public void setNextPosition(Point p) {
	nextPosition = p;
    }

    /**
     * Used to get the units current position
     * 
     * @return Point
     */
    public Point getPosition() {
	return position;
    }

    /**
     * Used to check the units current status
     * 
     * @return Boolean
     */
    public Boolean isDead() {
	return dead;
    }

    //
    // Other methods
    //

    /**
     * Used to give damage or heal the unit
     * 
     * @param hp
     */
    public void modifyHealth(int hp) {
	health += hp;
	dead = (health > 0) ? false : true;
    }

    /**
     * halts the unit for one timestep
     */
    public void haltUnit() {
	currentSpeed = 0;
    }

    /**
     * Method that ensures that the step is executed correctly
     */
    public void move() {
	Zone t = null;
	moveUnitFrom();

	// Checks if unit is movable
	if (currentSpeed == 1) {
	    // Calculates the new Position
	    takeStep();
	    // Gets the current brick and activates landOn

	    t = p.getCurrentZone(position);
	    t.getBrick().landOn(this);

	}

	// Reset currentSpeed after move
	currentSpeed = speed;

	moveUnitTo();
    }

    /**
     * Method to calculate the next position the standard implementation uses a
     * memory algorithm needs to handle setNextPosition
     */
    private void takeStep() {
	visited.add(position.hashCode());

	if (nextPosition == null) {
	    if (checkMovable(p.getPosToNorth(position))
		    && !visited.contains(new Point(position.x, position.y + 1)
			    .hashCode())) {
		path.push((Point) position.clone());
		position.move(position.x, position.y + 1);

	    } else if (checkMovable(p.getPosToSouth(position))
		    && !visited.contains(new Point(position.x, position.y - 1)
			    .hashCode())) {
		path.push((Point) position.clone());
		position.move(position.x, position.y - 1);

	    } else {
		boolean madeMove = false;
		while (!madeMove) {
		    if (checkMovable(p.getPosToEast(position))
			    && !visited.contains(new Point(position.x + 1,
				    position.y).hashCode())) {
			path.push((Point) position.clone());
			position.move(position.x + 1, position.y);
			madeMove = true;

		    } else if (checkMovable(p.getPosToWest(position))
			    && !visited.contains(new Point(position.x - 1,
				    position.y).hashCode())) {
			path.push((Point) position.clone());
			position.move(position.x - 1, position.y);
			madeMove = true;

		    } else if (checkMovable(p.getPosToNorth(position))
			    && !visited.contains(new Point(position.x,
				    position.y + 1).hashCode())) {
			path.push((Point) position.clone());
			position.move(position.x, position.y + 1);
			madeMove = true;

		    } else {
			// Try/catch to prevent crashes from
			// EmptyStackException if path is blocked
			try {
			    position = path.pop();
			    madeMove = true;
			} catch (EmptyStackException e) {
			    System.out.println("dead");
			    kill();
			    madeMove = true;
			}
		    }
		}
	    }
	} else {
	    position = (Point) nextPosition.clone();
	    nextPosition = null;
	}
    }

    /**
     * Method that the GUI uses to interact with the unit
     */
    public abstract void interact();

    /**
     * kills the unit
     */
    public void kill() {
	moveUnitFrom();
	dead = true;
    }

    //
    // Helper methods
    //

    /**
     * Method to remove the unit from the current
     * 
     * @see Zone
     */
    private void moveUnitTo() {
	Zone t = p.getCurrentZone(position);
	t.addUnit(this);
    }

    /**
     * Method to add the unit to the new
     * 
     * @see Zone
     */
    protected void moveUnitFrom() {
	Zone t = p.getCurrentZone(position);
	t.removeUnit(this);
    }

    /**
     * Method to check if current zone is movable
     * 
     * @param z
     * @return true if movable
     */
    protected Boolean checkMovable(Zone z) {
	if (z != null) {
	    return z.getBrick().isMovable();
	}
	return false;
    }

}
